package cn.hcbycloud.common.permission.filter;

import cn.hcbycloud.common.core.config.IgnoreUrlsConfig;
import cn.hcbycloud.common.core.constant.CommonConstant;
import cn.hcbycloud.common.core.exception.CustomizeException;
import cn.hcbycloud.common.core.utils.UrlPathMatcherUtils;
import cn.hcbycloud.common.jwt.service.encryptionkey.BaseGenerateJwtKeyService;
import cn.hcbycloud.common.jwt.service.jwt.JwtService;
import cn.hcbycloud.common.jwt.vo.LoginInfo;
import cn.hcbycloud.common.permission.utils.AccessContext;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import org.apache.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * @author gaoh
 * @desc 统一身份拦截
 * @date 2022年02月06日 上午 9:56
 */
@AllArgsConstructor
public class LoginUserInfoInterceptor implements HandlerInterceptor {
    private final static String TOKEN_CHECK_MESSAGE = "认证中心令牌校验错误";
    private final static String URL_CHECK_MESSAGE = "该接口需要无认证信息，认证失败";
    private JwtService jwtService;
    private BaseGenerateJwtKeyService generateJwtKeyService;

    /**
     * controller被请求前调用
     *
     * @param request
     * @param response
     * @param handler
     * @return
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 如果网关全局拦截器判断token已经失效，会在头信息中传送refreshedToken
        String refreshedToken = request.getHeader(CommonConstant.THREAD_LOCAL_TOKEN_REFRESHED);
        // 网关全局拦截器判断token无论是否失效，都将接口中的原始token从头信息中传递过来
        String token = request.getHeader(CommonConstant.THREAD_LOCAL_TOKEN);
        String jwtToken = null;
        /**
         * refreshedToken和token都存在，说明前端传入的token过期了，网关进行了刷新生成了新的refreshedToken
         * 但过期的token，网关也一并传过来了
         */
        if (StrUtil.isNotBlank(refreshedToken) && StrUtil.isNotBlank(token)) {
            // 由于老token已经过期，如果需要解析，只能使用refreshedToken
            jwtToken = refreshedToken;
            AccessContext.setRefreshedToken(refreshedToken);
        }
        /**
         * 如果refreshedToken为空，则表示前端接口传入的token没有过期，网关没有重新生成新的token
         * 解析老token即可
         */
        if (StrUtil.isBlank(refreshedToken) && StrUtil.isNotBlank(token)) {
            jwtToken = token;
        }
        /*判断是否为既没有携带token又不是忽略鉴权的URL,如果是的话，需要抛出异常*/
        if (StrUtil.isBlank(jwtToken) && !pathMath(request, IgnoreUrlsConfig.NACOS_IGNORE_URLS)) {
            throw new CustomizeException(URL_CHECK_MESSAGE, HttpStatus.SC_UNAUTHORIZED);
        }
        if (StrUtil.isNotBlank(jwtToken)) {
            AccessContext.setToken(jwtToken);
            try {
                LoginInfo loginInfo = jwtService.getLoginInfoToken(jwtToken, generateJwtKeyService);
                AccessContext.setLoginInfo(loginInfo);
            } catch (Exception e) {
                throw new CustomizeException(TOKEN_CHECK_MESSAGE, HttpStatus.SC_UNAUTHORIZED);
            }
        }
        /**
         * 如果请求头里没有token，代表这类请求被设置为没有token的，比如认证、注册接口，这类请求网关是放行的
         * 这类请求，拦截器也拦截，由于没有token，拦截器不做任何处理，直接放行
         */
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

    }

    /**
     * 请求结束之后调用，用于清理资源
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        AccessContext.clearToken();
        AccessContext.clearLoginInfo();
    }

    /**
     * 匹配需要鉴权的路径
     *
     * @param request
     * @param ignoreUrls
     * @return
     */
    private boolean pathMath(HttpServletRequest request, List<String> ignoreUrls) {
        if (CollUtil.isNotEmpty(ignoreUrls)) {
            for (String ignoreUrl : ignoreUrls) {
                if (UrlPathMatcherUtils.matchUrlSuffix(ignoreUrl, request.getRequestURI())) {
                    return true;
                }
            }
        }
        return false;
    }
}